9-1 nestjs 后端注册登录简介:登录鉴权相关概念
登录与鉴权的本质
登录(Login) 是用户向服务器证明自己身份的过程 — 用户在客户端输入用户名和密码,服务器将凭据与数据库中的记录进行比对,比对成功即完成身份确认。
鉴权(Authentication) 是验证请求方是否拥有访问权限的过程。鉴权工作必须在服务端完成,因为前端代码在浏览器中完全可见,任何客户端鉴权都可以被绕过。
客户端(浏览器) 服务端
┌─────────────────┐ ┌─────────────────┐
│ 输入用户名/密码 │ ── 请求 ──→ │ 比对数据库凭据 │
│ │ │ │
│ 收到 Token │ ←── 响应 ── │ 签发 JWT Token │
│ │ │ │
│ 携带 Token 请求 │ ── 请求 ──→ │ 验证 Token 有效性 │
│ 受保护资源 │ ←── 响应 ── │ 返回资源数据 │
└─────────────────┘ └─────────────────┘
text
核心概念辨析
鉴权、加密算法、HTTPS 是三个不同层次的解决方案,经常被混淆:
| 概念 | 定位 | 典型技术 | 解决的问题 |
|---|---|---|---|
| 鉴权 | 身份验证方式 | Session/Cookie、JWT、OAuth | 谁在访问?有没有权限? |
| 加密算法 | 数据变换工具 | Base64、MD5、RSA、AES | 数据如何不被明文暴露? |
| HTTPS | 通信安全协议 | HTTP + TLS/SSL | 传输信道如何防窃听和篡改? |
四种主流鉴权方式对比
1. Session / Cookie
传统的有状态鉴权方案。服务器创建 Session 并存储用户信息,通过 Cookie 将 Session ID 返回给客户端。
// Express + express-session 传统实现
import express from 'express'
import session from 'express-session'
const app = express()
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false,
cookie: { secure: true, maxAge: 3600000 } // 1 小时
}))
app.post('/login', (req, res) => {
const { username, password } = req.body
// 验证用户...
req.session.userId = user.id // 服务端存储
res.json({ message: '登录成功' })
})
typescript
| 优点 | 缺点 |
|---|---|
| 实现简单,生态成熟 | 服务器需存储 Session,占用内存 |
| 浏览器原生支持 Cookie | 多服务器部署需 Session 同步(需 Redis) |
| 跨平台困难(移动端/App 不便管理 Cookie) | |
| CSRF 攻击风险 |
2. JWT(JSON Web Token)
无状态令牌方案。服务器签发包含用户信息的 Token,客户端在请求头中携带,服务器无需存储 Session。
// NestJS JWT 鉴权实现
// auth.module.ts
import { Module } from '@nestjs/common'
import { JwtModule } from '@nestjs/jwt'
import { PassportModule } from '@nestjs/passport'
import { AuthService } from './auth.service'
import { AuthController } from './auth.controller'
import { JwtStrategy } from './jwt.strategy'
import { UsersModule } from '../users/users.module'
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: process.env.JWT_SECRET || 'your-secret-key',
signOptions: { expiresIn: '7d' },
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
typescript
// auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
import { UsersService } from '../users/users.service'
import * as bcrypt from 'bcrypt'
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService
) {}
async validateUser(username: string, password: string): Promise<any> {
const user = await this.usersService.findByUsername(username)
if (user && await bcrypt.compare(password, user.password)) {
const { password: _, ...result } = user
return result
}
return null
}
async login(user: any) {
const payload = { username: user.username, sub: user.id }
return {
access_token: this.jwtService.sign(payload),
}
}
async register(username: string, password: string, email: string) {
const hashedPassword = await bcrypt.hash(password, 10)
const user = await this.usersService.create({
username,
password: hashedPassword,
email,
})
const { password: _, ...result } = user
return result
}
}
typescript
// auth.controller.ts
import {
Body, Controller, Get, HttpCode, HttpStatus, Post, Request, UseGuards
} from '@nestjs/common'
import { AuthService } from './auth.service'
import { JwtAuthGuard } from './jwt-auth.guard'
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('register')
async register(
@Body() body: { username: string; password: string; email: string }
) {
return this.authService.register(body.username, body.password, body.email)
}
@HttpCode(HttpStatus.OK)
@Post('login')
async login(@Body() body: { username: string; password: string }) {
const user = await this.authService.validateUser(body.username, body.password)
if (!user) {
throw new UnauthorizedException('用户名或密码错误')
}
return this.authService.login(user)
}
@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@Request() req) {
return req.user
}
}
typescript
// jwt.strategy.ts — Passport JWT 策略
import { ExtractJwt, Strategy } from 'passport-jwt'
import { PassportStrategy } from '@nestjs/passport'
import { Injectable } from '@nestjs/common'
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET || 'your-secret-key',
})
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username }
}
}
typescript
// jwt-auth.guard.ts — 自定义守卫
import {
CanActivate, ExecutionContext, Injectable, UnauthorizedException
} from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
import { Request } from 'express'
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private readonly jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest()
const token = this.extractTokenFromHeader(request)
if (!token) {
throw new UnauthorizedException('未提供认证令牌')
}
try {
const payload = await this.jwtService.verifyAsync(token)
request['user'] = payload
} catch {
throw new UnauthorizedException('令牌无效或已过期')
}
return true
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? []
return type === 'Bearer' ? token : undefined
}
}
typescript
| 优点 | 缺点 |
|---|---|
| 无状态,服务端不存储 Session | Token 被截获后在有效期内可被冒用(中间人攻击) |
| 天然支持跨端(Web、App、小程序) | Token 刷新机制较复杂 |
| 可承载丰富信息(用户角色、权限等) | Payload 过大会增加传输开销 |
| 多服务器部署无需同步 | 无法主动让已签发的 Token 失效 |
3. OAuth 2.0
第三方授权登录方案(微信登录、GitHub 登录等)。用户在第三方平台完成身份验证,本系统获取授权令牌访问用户信息。
┌────────┐ ①跳转授权页 ┌────────────┐
│ 用户 │ ──────────────→ │ 第三方平台 │
│ (客户端) │ │ (GitHub等) │
└────────┘ ←────────────── └────────────┘
↑ ②用户授权 │
│ ③回调 code ↓
┌────────┐ ④返回 Token ┌────────────┐
│ 本系统 │ ←────────────── │ 授权服务器 │
│(后端) │ ──⑤获取用户信息──→ │ │
└────────┘ └────────────┘
text
| 优点 | 缺点 |
|---|---|
| 开放标准,任何平台均可接入 | 需要额外部署授权服务器 |
| 不涉及用户密码,安全性高 | 增加网络请求(回调流程) |
| 集成简单(RESTful API 文档) | 用户依赖第三方平台可用性 |
| 可精确控制授权范围 |
鉴权方式选型指南
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 传统 Web 应用(仅浏览器) | Session/Cookie | 浏览器原生支持,简单可靠 |
| 移动端 + Web + 小程序(跨端) | JWT | 无状态,天然跨端 |
| 需要第三方社交登录 | OAuth 2.0 | 标准授权协议 |
| 企业级应用(高安全要求) | JWT + OAuth | 多因素鉴权,分层防护 |
关键要点
- 鉴权 ≠ 加密 ≠ HTTPS — 三者解决不同层面的问题,通常组合使用
- 鉴权必须在服务端完成 — 前端代码可被查看和篡改,不能作为安全边界
- JWT 是当前跨端应用的主流选择 — 无状态、易扩展、支持移动端
- 安全性越高,用户体验越低 — 短信验证码、二次鉴权等措施在提高安全性的同时增加了操作步骤
- 本章重点学习 JWT 在 NestJS 中的完整实现,包括注册、登录、Token 签发与验证
↑